﻿using System;
using System.Windows.Forms;
using System.Drawing;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace IsreversI.Visualisation
{
    /// <summary>
    /// View that keeps track of what the game board looks like.
    /// </summary>
    class BoardVisualView : GameLogic.BoardModelAttachment
    {
        #region member variables
        public delegate void VisualEventHandler(object sender, VisualEventArgs vea);
        private GameLogic.GameStateView stateView;
        private IDictionary<TileState, PaintEventHandler> drawInstructions;
        private bool enableDecoration = false;
        #endregion

        #region events
        // Image updated in a hundred different ways
        public VisualEventHandler[,] TileObjectAdded;
        public VisualEventHandler[,] TileObjectRemoved;
        public event VisualEventHandler AllObjectsRemoved;
        public event BoardEventHandler NewBoardInPlace;
        #endregion

        #region properties
        public Size Size
        {
            get
            {
                return BoardModel.BoardSize;
            }
        }

        public bool EnableDecoration
        {
            get
            {
                return enableDecoration;
            }
            set
            {
                if (enableDecoration != value)
                {
                    enableDecoration = value;
                    this.SyncWithModel();
                }
            }
        }
        #endregion

        #region constructors
        public BoardVisualView(GameLogic.BoardModel model)
        {
            this.stateView = new GameLogic.GameStateView(model);
            this.Initialize();
            this.handleModelChange(model);

            this.SyncWithModel();
        }

        public BoardVisualView(GameLogic.GameStateView stateView)
        {
            this.stateView = stateView;
            this.Initialize();
            this.handleModelChange(this.stateView.BoardModel);

            this.SyncWithModel();
        }

        private void Initialize()
        {
            this.drawInstructions = new Dictionary<TileState, PaintEventHandler>();
            this.drawInstructions[TileState.Red] = (object sender, PaintEventArgs pea) =>
            {
                pea.Graphics.DrawImage(IsreversI.Properties.Resources.redstone, pea.ClipRectangle);
            };
            this.drawInstructions[TileState.Blue] = (object sender, PaintEventArgs pea) =>
            {
                pea.Graphics.DrawImage(IsreversI.Properties.Resources.bluestone, pea.ClipRectangle);
            };
            this.drawInstructions[TileState.MovePossible] = (object sender, PaintEventArgs pea) =>
            {
                Size tilesize = pea.ClipRectangle.Size;
                int offset = 10;
                pea.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
                pea.Graphics.DrawEllipse(Pens.Black, new Rectangle(offset, offset, tilesize.Width-offset*2, tilesize.Height-offset*2));
            };
            this.drawInstructions[TileState.Invalid] = (object sender, PaintEventArgs pea) =>
            {
                pea.Graphics.DrawString("Error", SystemFonts.DefaultFont, Brushes.Black, new PointF(0, 0));
            };

            this.PreBoardModelChanged += this.stateView.acceptNewModel;
            this.stateView.PreBoardModelChanged += this.handleStartModelChange;
            this.stateView.PostBoardModelChanged += this.acceptNewModel;
        }
        #endregion

        #region event handlers
        private void handleStartModelChange(object sender, GameLogic.BoardEventArgs bea)
        {
            this.BoardModel.StonePlayed -= this.handlePiecePlayed;
            this.BoardModel.StoneRemoved -= this.handlePieceRemoved;
            this.BoardModel.StoneFlipped -= this.handlePieceRemoved;
            this.BoardModel.StoneFlipped -= this.handlePiecePlayed;
            this.stateView.PossibleMovesUpdated -= this.handleDecorationUpdated;
        }

        private void handlePiecePlayed(object o, GameLogic.BoardEventArgs bea)
        {
            VisualEventArgs vea = new VisualEventArgs();
            vea.DrawCode = this.GetEffectsOnState(bea.NewState);
            vea.Point = bea.Point;
            if (this.TileObjectAdded[vea.Point.X, vea.Point.Y] != null)
                this.TileObjectAdded[vea.Point.X, vea.Point.Y](this, vea);
        }

        private void handlePieceRemoved(object o, GameLogic.BoardEventArgs bea)
        {
            VisualEventArgs vea = new VisualEventArgs();
            vea.DrawCode = this.GetEffectsOnState(bea.OldState);
            vea.Point = bea.Point;
            if (this.TileObjectRemoved[vea.Point.X, vea.Point.Y] != null)
                this.TileObjectRemoved[vea.Point.X, vea.Point.Y](this, vea);
        }

        private void handleDecorationUpdated(object o, GameLogic.BoardEventArgs bea)
        {
            if (!this.EnableDecoration)
                return;
            this.handlePieceRemoved(o, bea);
            this.handlePiecePlayed(o, bea);
        }
        #endregion

        #region public methods
        /// <summary>
        /// Return all handlers that should be bound to that position.
        /// </summary>
        public ISet<PaintEventHandler> GetEffectsOnTile(int x, int y)
        {
            return this.GetEffectsOnState(this.GetStateAtPosition(x, y));
        }

        public ISet<PaintEventHandler> GetEffectsOnTile(Point p)
        {
            return this.GetEffectsOnTile(p.X, p.Y);
        }
        #endregion

        #region protected methods
        protected override void handleModelChange(GameLogic.BoardModel model)
        {
            base.handleModelChange(model);
            this.TileObjectAdded = new VisualEventHandler[model.BoardSize.Width, model.BoardSize.Height];
            this.TileObjectRemoved = new VisualEventHandler[model.BoardSize.Width, model.BoardSize.Height];

            model.StonePlayed += this.handlePiecePlayed;
            model.StoneRemoved += this.handlePieceRemoved;
            model.StoneFlipped += this.handlePieceRemoved;
            model.StoneFlipped += this.handlePiecePlayed;

            if (NewBoardInPlace != null)
                NewBoardInPlace(this, new GameLogic.BoardEventArgs(model));
            this.stateView.PossibleMovesUpdated += this.handleDecorationUpdated;
            this.SyncWithModel();
        }
        #endregion

        #region private methods
        private void SyncWithModel()
        {
            if (this.BoardModel == null)
                return;
            if (this.AllObjectsRemoved != null) // Tell everyone that everything we ever promised them is a lie.
                this.AllObjectsRemoved(this, new VisualEventArgs());
            for (int xC = 0; xC < this.BoardModel.BoardSize.Width; ++xC)
                for (int yC = 0; yC < this.BoardModel.BoardSize.Height; ++yC)
                {
                    VisualEventArgs vea = new VisualEventArgs();
                    vea.DrawCode = this.GetEffectsOnTile(xC, yC);
                    vea.Point = new Point(xC, yC);
                    if (this.TileObjectAdded[xC, yC] != null)
                        this.TileObjectAdded[xC, yC](this, vea);
                }
        }

        private TileState GetStateAtPosition(int x, int y)
        {
            if (this.EnableDecoration)
                return this.stateView.DecoratedTileAt(x, y);
            else
                return this.BoardModel[x, y];
        }

        private TileState GetStateAtPosition(Point p)
        {
            return this.GetStateAtPosition(p.X, p.Y);
        }

        private ISet<PaintEventHandler> GetEffectsOnState(TileState state)
        {
            ISet<PaintEventHandler> layers = new HashSet<PaintEventHandler>();
            foreach (KeyValuePair<TileState, PaintEventHandler> kvp in this.drawInstructions)
                if (state.HasFlag(kvp.Key))
                    layers.Add(kvp.Value);
            return layers;
        }
        #endregion
    }
}
